#include <sys/types.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#include "common.h"
#include "server.h"
 
#define MAX_BUFFER_SIZE 1600

 
client aClients[FD_SETSIZE]; // Liste des clients
			    // FD_SETSIZE est propre au systeme (UNIX = 1024 ?!)
int size = 0;               // Nombre de descripteurs "actifs" 
int charsetNumber[15];
unsigned char hash[16];
int min, max; 

char buffer[MAX_BUFFER_SIZE]; // buffer de lecture / ecriture

unsigned long long jobIndex = 0;
unsigned long long nbJob ;

unsigned long long JOBSIZE = 10000000;
void printCList()
{
	int i;
	printf("**** Client list ****\n");
	for(i=1; i<size; i++) {
		printf("%02i - %s \t Score : %f\n", aClients[i].sock, aClients[i].adress, aClients[i].benchScore);
	}
	printf("*********************\n");
}

void giveJob()
{
	int j;
	if(jobIndex < nbJob)
	{
		for(j = 1; j < size; j++)
		{
			if(aClients[j].state == WAIT)
			{
				job_t nextJob;
				
				nextJob.pwdMin = min;
				nextJob.pwdMax = max;
				memcpy(nextJob.charsetNumber, charsetNumber, sizeof(charsetNumber));
				nextJob.start = jobIndex;
				nextJob.stop = jobIndex+JOBSIZE;
				strcpy(nextJob.hash, hash);
				nextJob.state = RUNNING;
										
				printf("next client: %i\n", j);
				buffer[0] = 'M';
				memcpy(buffer+1, &nextJob, sizeof(job_t));
				
				printf("client %i %s\n", aClients[j].sock, aClients[j].adress);
				
				if(send(aClients[j].sock, buffer,1024,0) == -1)
				{
					
					perror("Erreur lors de l'appel a send -> ");
					exit(1);
				}
				else
				{
					
					aClients[j].state = BUSY;
					aClients[j].job = nextJob;
					printf("job sent: %llu\n", nextJob.start);
					jobIndex += JOBSIZE;
				}
				break;
			}
			
		}
	}
	else
	{
		for(j = 1; j < size; j++)
		{
			if(aClients[j].state == BUSY)
			{
				return;				
			}
			printf("Plus de job, plus de clients occupés, fin du programme");
			exit(0);			
		}
	}
}

int hexDigitToInt(char hexDigit)
{
	switch(hexDigit)
	{
		case '0': return 0;
		case '1': return 1;
		case '2': return 2;
		case '3': return 3;
		case '4': return 4;
		case '5': return 5;
		case '6': return 6;
		case '7': return 7;
		case '8': return 8;
		case '9': return 9;
		case 'a': return 10;
		case 'b': return 11;
		case 'c': return 12;
		case 'd': return 13;
		case 'e': return 14;
		case 'f': return 15;
		default : -1;
	}
}
 
int main(int argc, char **argv)
{
	int sockfd, sockfd2;        
	fd_set readfds, std;            

	job_t jobs[MAX_BUFFER_SIZE];


	struct sockaddr_in my_addr; // structure d'adresse => identite du serveur
	struct sockaddr_in client;  // structure d'adresse => identite du serveur
	socklen_t sin_size;         // taille d'une structure sockaddr_in
                  
	int sockmax = 0;              // identifiant maximal de socket
	int i;
	int msgSize;

	
	/* initialisation de la memoire du buffer*/
	memset(buffer, '\0',MAX_BUFFER_SIZE);
	/* taille d'une structure sockaddr_in utile pour la fonction recvfrom */
	sin_size = sizeof(struct sockaddr_in);
 
	// Verification du nombre d'arguments. On doit specifier un port seulement 
	if(argc != 5) {
		printf("Usage: %s pwdMin pwdMax mask hash\n\tLaunch bruteforce attack on hash with a lenght between pwdMin and pwdMax using mask\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	
	memset(charsetNumber,-1,sizeof(charsetNumber));

	min = atoi(argv[1]);
	max = atoi(argv[2]);
	
	int j=0;
	while(argv[3][j] == '?'){
		switch(argv[3][j+1]){
			case 'l':
				charsetNumber[j/2] = 0;
				break;
			case 'u':
				charsetNumber[j/2] = 1;
				break;
			case 'd':
				charsetNumber[j/2] = 2;
				break;
			case 's':
				charsetNumber[j/2] = 3;
				break;
			default:
				charsetNumber[j/2] = -1;
				break;
		}
		j += 2;
	}
	
	nbJob = nbElement(charsetNumber);
	
	int pos = 0;
	for(i = 0; i < 16; i++)
	{
		int high = hexDigitToInt(argv[4][pos]);
		//printf("high: %i\n", high);
		int low = hexDigitToInt(argv[4][pos+1]);
		//printf("low: %i\n", low);
		hash[i] = (high << 4)+low;
		//printf("res: %i\n", hash[i]);
		pos += 2;
	}
	    

	for (i = 0; i < 16; i++)
		printf ("%02x", hash[i]);
	printf("\n");
	
	
	//verification de l'integrite des arguments : port valide
	errno = 0;
	/*port = strtol(argv[1], NULL, 10);
	if(errno || port < 1 || port > 65535) {
		fprintf(stderr,"error : listen port must be between 1 and 65535\n");
		exit(EXIT_FAILURE);
	}*/
 
	// creation de la socket principale pour connexions entrantes
	sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(sockfd ==- -1) {
		perror("socket()");
		exit(EXIT_FAILURE);
	}
 
	// initialisation de la structure d'adresse du serveur
	my_addr.sin_family = AF_INET; // famille d'adresse : internet 
	my_addr.sin_port = ntohs(6969); // port du serveur (format reseau)
	my_addr.sin_addr.s_addr=htonl(INADDR_ANY); // association sur toute ip
 
	// association de la socket avec l'adresse reseau du serveur
	if(bind(sockfd,(struct sockaddr *) &my_addr,sizeof(my_addr)) ==-1) {
		perror("bind()");
		exit(EXIT_FAILURE);
	}
 
	// placement de la socket en mode ecoute (file d'attente max: 10)
	if(listen(sockfd, 10) == -1) {
		perror("listen()");
		exit(EXIT_FAILURE);
	}
	printf("Server started\nWaiting ...\n");
  
	aClients[0].sock = sockfd; // ajout de la socket principale au tableau d'ecoute
	size++;
	

	
 
	// debut de boucle d'exécution du serveur
	while(1)
	{
		// definition de l'ensemble de descripteurs a ecouter depuis tabsockfd
		// et recuperation de la valeur max du descripteur a ecouter
		FD_ZERO(&readfds);         //liste videe
		FD_SET(0,&readfds); 
		sockmax = aClients[0].sock;
		for(i=0; i<size; i++){     // parcours des descripteurs de tabsockfd
			FD_SET(aClients[i].sock, &readfds); // ajout de la socket a la liste 
			if(sockmax < aClients[i].sock) sockmax = aClients[i].sock; // socket max
		}
		// mise en place d'une ecoute sur toutes les socket de readfds
		// select() met le programme en attente d'un evenement. 
		if(select(sockmax+1, &readfds, NULL, NULL, NULL) == -1){
			perror("select()");
			exit(EXIT_FAILURE);
		}

 		if(FD_ISSET(0,&readfds)){ // si l'entree standard est dans readfds,
 		//alors l'utilisateur en en train de rédiger un message a envoyer
			if(read(0,buffer,1024) == -1){                             // on lit donc ce qui arrive sur l'entrée standard
				perror("Erreur lors de l'appel a read -> ");
				exit(1);
			}
			if (buffer[0] == 'P')
			{ // Affichage de la liste des clients
				printCList();
			}
			else if (buffer[0] == 'M')
			{ 
				giveJob();
			} else {
				printf("Incorrect command\n");  
			}
		} 
		// Attente d'une nouvelle demande de connexion
		if(FD_ISSET(sockfd,&readfds)){
			sockfd2 = accept(sockfd,(struct sockaddr*) &client, &sin_size);
			if((sockfd2) == -1){
				perror("accept()");
				exit(EXIT_FAILURE);
			}
			printf("Connection established with %s\n", inet_ntoa(client.sin_addr));
			aClients[size].sock = sockfd2; // ajout au tableau d'ecoute
			strcpy(aClients[size].adress, inet_ntoa(client.sin_addr)); // On stocke l'adresse
			aClients[size].benchScore = 0; // On met à jour le benchmark
			aClients[size].state = WAIT;
			
			size++;
			giveJob();
			printCList();

			// envoi d'un message de bienvenue
			/*int msgSize = snprintf(buffer, 22, "Welcome!!\n");
			if(send(sockfd2, &buffer, msgSize,0) == -1){      
                		perror("send()");
				exit(EXIT_FAILURE);
			}*/
		} 

		// sinon il s'agit d'une autre socket : comm. client -> serveur
		// section a completer selon les communications a mettre en place
		// protocole applicatif
		for(i=1; i<size; i++){
			if(FD_ISSET(aClients[i].sock, &readfds)){ // recherche du descripteur 
				msgSize = recv(aClients[i].sock, &buffer, MAX_BUFFER_SIZE, 0);
				if (size == -1){
					perror("recv()");
					exit(EXIT_FAILURE);
				}
				// message de taille 0->connexion fermee par le client
				if(msgSize == 0) {
					printf("peer %02i has terminated connexion\n", aClients[i].sock);
					close(aClients[i].sock); // fermeture de la socket
					aClients[i].sock = aClients[size-1].sock; // maj tableau
					size--;
				} else {
					if (buffer[0] == 'D')
					{
						aClients[i].state = WAIT;
						aClients[i].job.state = DONE;
						job_t j;
						memcpy(&j, buffer+1, sizeof(job_t));
						aClients[i].job = j;
						printf("job %llu done\n", aClients[i].job.start);
						if(aClients[i].job.res != -1)
						{
							char mot[16];
							
							indiceToword(mot, aClients[i].job.charsetNumber, aClients[i].job.res);
							printf("Trouvé! indice: %i mot: %s\n", aClients[i].job.res, mot);
							exit(1);
						}
						giveJob();
					}
					else
					{
						buffer[msgSize] = '\0'; // ajout du '\0' pour affichage 
						printf("decr %02i (%s) : %s", aClients[i].sock, aClients[i].adress, buffer);
					}
				}
			}
		}
	}
}
